<?php
/*
$Id: ctg-fight-game.php 49 2011-09-02 13:16:53Z eugim.migue@gmail.com $
*/

set_time_limit(1200);

require_once 'quickstart-loading-re.php';


function _has_loser($tuple){
	return re::isEqual('L', $tuple->get('status'));
}

//if there is at least one loser, then there is/are winner(s)
function has_winner($logs){
	
	return compute_result($logs)
			->where('_has_loser')
			->count() >= 1;
}

function happened($percentage){

	$base = 100;
	$_p    = $base * (round($percentage, 2) > 1 ? 1 : $percentage);

	if( 0 == $_p || 1 == $_p ){
		return (bool) $_p;
	}
	
	return mt_rand(1, $base) <= $_p;
}

function get_tale_of_the_tape($player){

	$_stats = array();
	$stats   = $player->get('player_stats_range')->toArray();
	foreach($stats as $name=>$range){
		$_stats[$name] = re::isEqual($name, 'evade') ? 
		(re::invokeOp('mt_rand', $range) / 100) : re::invokeOp('mt_rand', $range);
	}
	
	return re::tuple($_stats);
}

function encounter($att_def){
	
	$att_player_type  = $att_def->get('off_player_type');
	$def_player_type = $att_def->get('def_player_type');
	
	$off_stats  = $att_def->get('off_player_stats_curr');
	$def_stats = $att_def->get('def_player_stats_curr');
	
	$off_attack = re::invokeOp(
		'mt_rand',
		$att_def->get('off_stats_range')->get('attack')
	);
	
	$attack_increased = FALSE;
	if(
			 re::isEqual($att_player_type, 'ninja') 
			//attack increasing twice
		&& happened(.05) 
	) {
		$off_attack = $off_attack * 2;
		$attack_increased = TRUE;
	}
	
	$attack_evaded = happened($def_stats->get('evade'));
	$def_health       = $def_stats->get('health');
	$def_defence     = $def_stats->get('defence');
	$health_increased = FALSE;
	
	if( $attack_evaded ){
		if(
			re::isEqual($def_player_type, 'samurai') &&
			//regained health
			happened(.1)	
		){
			$def_health = $def_health + 10;
			$health_increased = TRUE;
		}
	}
	else{
		$deduct = ($off_attack - $def_defence);
		$def_health = $def_health - ($deduct < 0 ? 0 : $deduct);
		$def_health = $def_health < 0 ? 0 : $def_health;
	}
	
	$def_inc = FALSE;
	if( re::isEqual($def_player_type, 'brawler') ){
		if ( $def_health < $def_stats->get('health')  * (1 - .2) ){
			$def_defence = $def_defence + 10;
			$def_inc = TRUE;
		}
	}
	
	return re::tuple(array(
		'def_player_stats_curr'=>$def_stats->substitute(array(
			'health'=>$def_health
		   ,'defence'=>$def_defence
		))
	   ,'a_d_summary'=>re::tuple(array(
			'attack_given'=>$off_attack
		   ,'is_evaded'=>$attack_evaded ? 'TRUE' : 'FALSE'
		   ,'attack_increased'=>$attack_increased ? 'TRUE' : 'FALSE'
		   ,'health_increased'=>$health_increased ? 'TRUE' : 'FALSE'
		   ,'defence_increased'=>$def_inc ? 'TRUE' : 'FALSE'
	   ))
	));
}

function fight($players){
	
	$players = $players->join(
		$players->rename(array(
			'player_stats'=>'player_stats_curr'
		))
	);
	
	$_players = $players
	->project(array('player_name', 'player_stats_curr'))
	->unwrap(array('player_stats_curr'))
	->toArray(array(
		'sort'=>array('speed'=>'desc', 'defence'=>'asc')
	));
	
	$turns = 0; $turns_limit = 30;
	$turn_id = 0;
	
	do{

		$turns++;
		
		foreach($_players as $_attacker){
		
			$turn_id++;
			
			$_a  = $_attacker->project(array(
				'player_name'
			))
			->toRelation();
			
			$attackers = $players->semijoin(
				$_a
			)
			->project(array('player_stats'), TRUE)			
			->rename(array(
				'player_type'=>'off_player_type'
			   ,'player_name'=>'off_fighter_name'
			   ,'player_stats_range'=>'off_stats_range'
			   ,'player_stats_curr'=>'off_player_stats_curr'
			))
			->join(re::relation(array(array('turn'=>$turns, 'turn_id'=>$turn_id))));
			
			$defenders = $players->semiminus(
				$_a
			)
			->project(array('player_stats'), TRUE)
			->rename(array(
				'player_type'=>'def_player_type'
			   ,'player_name'=>'def_fighter_name'
			   ,'player_stats_range'=>'def_stats_range'
			   ,'player_stats_curr'=>'def_player_stats_curr'
			));
			
			$attackers = re::val($attackers);
			$defenders = re::val($defenders);
			
			$_logs = $attackers->times(
				$defenders
			)
			->extend(array(
				'turn_summary'=>'encounter'
			), array(
				'turn_summary'=>re::AS_OP_EXPR
			))
			->project(array('def_player_stats_curr'), TRUE)
			->unwrap(array('turn_summary'));
			$_logs = re::val($_logs);
			
			$logs   = !isset($logs) ? $_logs : $logs->union($_logs);

			$players = $players->semijoin(
				$attackers->rename(array(
					'off_fighter_name'=>'player_name'
				))
			)->union(
				$players
				->project(array('player_stats_curr'), TRUE)
				->join(
					$_logs
					->project(array('def_fighter_name', 'def_player_stats_curr'))
					->rename(array('def_fighter_name'=>'player_name', 'def_player_stats_curr'=>'player_stats_curr'))
				)
			);
			$players = re::val($players);
			
			if( has_winner($_logs) ){
				break  2;
			}
		}
	
	}while($turns < $turns_limit);

	return $logs;
}

function compute_result($fight_plays){
	
	if(!function_exists('_status') ){
		function _status($tuple){
			return $tuple->current_health == 0 ? 'L' : 'W';
		}
	}
	
	return $fight_plays->project(array(
		'DEF_PLAYER_STATS_CURR', 'DEF_FIGHTER_NAME'
	))
	->unwrap(array('DEF_PLAYER_STATS_CURR'))
	->rename(array('DEF_FIGHTER_NAME'=>'FIGHTER_NAME'))
	->union(
		$fight_plays->project(array(
			'OFF_PLAYER_STATS_CURR', 'OFF_FIGHTER_NAME'
		))
		->unwrap(array('OFF_PLAYER_STATS_CURR'))
		->rename(array('OFF_FIGHTER_NAME'=>'FIGHTER_NAME'))
	)->summarizeBy(array(array('fighter_name')), array(
		'current_health'=>array('min', array('health'))
	))->extend(array(
		'status'=>'_status'
	), array(
		'status'=>re::AS_OP_EXPR
	));
}

function get_players($data = array()){

	if( empty($data) ){
		$data = array();
		foreach($_POST['player'] as $index=>$val){
			$data[] = array(
				'player_name'=>$val, 'player_type'=>$_POST['player_type'][$index]
			);
		}
		return $data;
	}
	
	return $data;
}

function players($data){
	
	/*
	$data = array(
		array('player_type'=>'ninja', 'player_name'=>'hanzo')
	   ,array('player_type'=>'samurai', 'player_name'=>'haohmaru')
	);
	*/

	$players  = re::relation($data);
	
	if( $players->count() < 2 ){
		throw new Exception('At least two players.');
	}
	
	if (
		$players
		->project(array('player_name'))
		->count() != $players->count()
	){
		throw new Exception('Players must have unique names.');
	}
	
	return $players;
	
}

$player_types = re::relation(array(
	array('player_type'=>'Ninja', 'player_stats_range'=>re::tuple(array('health'=>array('min'=>100, 'max'=>100), 'attack'=>array('min'=>60, 'max'=>70), 'defence'=>array('min'=>20, 'max'=>30), 'speed'=>array('min'=>90, 'max'=>100), 'evade'=>array('min'=>30, 'max'=>50))))
   ,array('player_type'=>'Samurai', 'player_stats_range'=>re::tuple(array('health'=>array('min'=>100, 'max'=>100), 'attack'=>array('min'=>75, 'max'=>80), 'defence'=>array('min'=>35, 'max'=>40), 'speed'=>array('min'=>60, 'max'=>80), 'evade'=>array('min'=>30, 'max'=>40))))
   ,array('player_type'=>'Brawler', 'player_stats_range'=>re::tuple(array('health'=>array('min'=>100, 'max'=>100), 'attack'=>array('min'=>65, 'max'=>75), 'defence'=>array('min'=>40, 'max'=>50), 'speed'=>array('min'=>40, 'max'=>65), 'evade'=>array('min'=>30, 'max'=>35))))
));

$player_types_ = $player_types->toArray(array('sort'=>array('player_type')));

$is_post = $_SERVER['REQUEST_METHOD'] == 'POST';

if( $is_post ){

try{
	
	$actual_players = players(get_players())
		->join($player_types)
		->extend(array(
			'player_stats'=>'get_tale_of_the_tape'
		), array(
			'player_stats'=>re::AS_OP_EXPR
		));

	$start = microtime(true);
	$actual_players = re::val($actual_players);
	$fight_plays      = fight($actual_players);
	$fight_result     = compute_result($fight_plays);
	$duration          = microtime(true) - $start;
}

catch(Exception $e){
	echo $e->getMessage() . " " . $e->getLine() . " "  . $e->getFile();
}

}

?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
	<title>
	Using re to implement a Fight Game
	</title>
</head>
<body>
<style type="text/css">
	* {
		font-family : Tahoma, verdana;
		font-size:12px;
	}
	
	a {
		text-decoration:none;
	}
	
	table {
		width: 100%;
		font-size: 12px;
		border: 1px solid #E5E5E5;
		border-collapse: collapse;
	}
	
	table td, table tr th {
		padding: 5px;
	}
	table thead td, table thead td a, table tr th, table tr th a {
		text-decoration: none;
		font-size: 12px;
		font-weight: bold;
		color:#FFFFFF;
	}
	table thead td, table tr th {
		border-bottom:  1px solid #E5E5E5;
	}
	
	table tbody td {
		border: 1px solid #E5E5E5;
	}
	
	table tbody tr:hover {
		background-color: #EEF9D6;
	}	
</style>

<div style="border:1px;float:left;">
	<h2>Specify Players</h2>
	<form method="post">
		<?php
		
			function _get(&$var, $def_val = ''){
				
				return !isset($var) ? $def_val : $var;		
			}
			
			function create_options($options, $val, $cfg){
			
				$_options = array();
				foreach($options as $tuple){
					$_options[] = sprintf(
						'<option value="%s" %s>%s</option>'
					   ,$tuple->get($cfg['value'])
					   ,$tuple->get($cfg['value']) == $val ? 'selected="selected"' : ''
					   ,$tuple->get($cfg['display'])
					);
				}
				return implode("", $_options);
			}
		?>
		Player 1 Name: <input type="text" name="player[]" value="<?php echo _get($_POST['player'][0], 'player 1'); ?>"/>
		Player 1 Type: <select name="player_type[]">
			<?php 				
				echo create_options(
					$player_types_				   
				   ,_get($_POST['player_type'][0], '')
				   ,array('value'=>'player_type', 'display'=>'player_type')
				);
			?>
		</select>
		<br/>
		Player 2 Name: <input type="text" name="player[]" value="<?php echo _get($_POST['player'][1], 'player 2'); ?>"/>
		Player 2 Type: <select name="player_type[]">
			<?php
				echo create_options(
					$player_types_				   
				   ,_get($_POST['player_type'][1], 'Samurai')
				   ,array('value'=>'player_type', 'display'=>'player_type')
				);			
			?>		
		</select>
		<br/>
		<div style="text-align:center;width:20%;"><input type="submit" value="Fight!"></div>
	</form>
</div>

<div style="margin-left:5px;float:left;width:40%;">
	<h2>Player Types</h2>
	<table>
		<tr>
		<?php
			$heading = $player_types->heading();
			$heading = array_intersect_key(array(
				'PLAYER_TYPE'=>'Player Type'
			   ,'PLAYER_STATS_RANGE'=>'Capabilities Range'
			), $heading);
			foreach($heading as $name=>$text){
		?>
			<td><?php echo $text; ?></td>
		<?php
			}
		?>
		</tr>
		<tbody>
	<?php
		foreach($player_types_ as $tuple){
	?>
			<tr>
				<?php
					foreach($heading as $name=>$text){
						$_v = $tuple->get($name);
						if( $name == 'PLAYER_STATS_RANGE' ){
				?>
						<td>
						<?php
							 $_v = $_v->toArray();
							 foreach($_v as $n=>$v){
								echo $n . ": " . implode(" - ", $v) . "<br/>";
							 }
						?>
						</td>
				<?php
						}
						else{
				?>
						<td><?php echo $_v; ?></td>
				<?php
						}
					}
				?>
			</tr>
	<?php
		}
	?>
		</tbody>
	</table>
</div>

<div style="clear:both;"></div>

<?php echo isset($duration) ? ('Fight lasted for ' . round($duration, 2) . ' second(s).') : '';?>
<p></p>
<?php

	if( isset($fight_result) ){
	
		$fight_result_   = $fight_result->toArray(array(
			'sort'=>array('current_health'=>'desc')
		));
	
?>
<div>
<div>Result:</div>
<table>
<tr>
		<td>Player</td>
		<td>Standing</td>
</tr>
<tbody>
<?php
	foreach($fight_result_ as $_result){
?>
	<tr>
		<td><?php echo $_result->get('fighter_name'); ?></td>
		<td><?php echo $_result->get('status'); ?></td>
	</tr>
<?php
	}
?>
</tbody>
</table>
<?php
	}
?>
</div>
<?php
	
	if( isset($fight_plays) ){
		
		$fight_plays_ = $fight_plays
			->project(
				array('OFF_PLAYER_STATS_CURR', 'OFF_STATS_RANGE', 'DEF_STATS_RANGE', 'DEF_PLAYER_TYPE', 'OFF_PLAYER_TYPE')
			   ,true
			);
			
		$heading = $fight_plays_->heading();
		
		$heading = array_intersect_key(array(
			'TURN_ID'=>'Turn ID'
		   ,'TURN'=>'Player Turn'
		   ,'OFF_FIGHTER_NAME'=>'Offensive Fighter'
		   ,'DEF_FIGHTER_NAME'=>'Defensive Fighter'
		   ,'DEF_PLAYER_STATS_CURR'=>'Defensive Fighter Status'
		   ,'A_D_SUMMARY'=>'Turn Summary'
		), $heading);
		
		$fight_plays_   = $fight_plays_->toArray();
?>
<p></p>
<div>Play-by-play:</div>
<table>
<tr>
	<?php
		foreach($heading as $name=>$text){
	?>
		<td><?php echo $text; ?></td>
	<?php } ?>
</tr>
<tbody>
<?php
foreach($fight_plays_ as $tuple){
?>
<tr>
	<?php foreach($heading as $name=>$type) {?>
		<td>
			<?php
				$_val = $tuple->get($name);
				if( is_scalar($_val) ){
					echo $tuple->get($name);
				}
				else{
					$_val = $_val->toArray();
					foreach($_val as $_n=>$_v){
						if( re::isEqual($_n, 'health') ){
			?>			
						<div>HEALTH:</div>
						<div style="color:white;padding:2px;border:1px solid black;width:<?php echo $_v <= 3 ? 3 : $_v; ?>%;background-color:<?php echo $_v <= 50 ? 'red' : 'green'; ?>;">
							<?php echo $_v; ?>
						</div>
			<?php
						}else{
							echo "{$_n} : " . (is_array($_v) ? implode(",", $_v) : $_v) . "<br/>";
						}
					}
				}
			?>
		</td>
	<?php }?>
</tr>
<?php
}
?>
</tbody>
</table>
<?php
	}
?>
</body>
</html>